home *** CD-ROM | disk | other *** search
/ SGI Hot Mix 14 / Hot Mix 14.iso / .all / bin / Game / SlidePuzzle.java (.txt) < prev   
Text File  |  1996-07-23  |  23KB  |  717 lines

  1. import java.awt.*;
  2. import java.applet.*;
  3. import java.awt.image.*;
  4.  
  5. /**
  6.  * The definitive (at least until I write version 2.0) Slide Puzzle
  7.  * class.  Allows for specification of the image in the slide puzzle,
  8.  * the size (number of rows and columns), as well as the sound to use
  9.  * when scrambling the puzzle, when moving tiles, and when the puzzle
  10.  * is solved.  Fully animated tile slides, and allows multiple tiles 
  11.  * to be slid on one mouse click.
  12.  *<pre>
  13.  * Revision History
  14.  *      1.2 -- Changed puzzle to extend Canvas to make it a component
  15.  *             that can be used anywhere.
  16.  *      1.1 -- Added support for different images, sounds, and different
  17.  *             puzzle sizes.
  18.  *      1.0 -- Initial version, required a graphics context to be passed
  19.  *             in for drawing.
  20.  *</pre>
  21.  * @author Andrew Wack
  22.  * @version 1.2 May 8, 1996
  23.  */
  24. class SlidePuzzle extends Canvas implements Runnable {
  25.  
  26.    /*
  27.     * Sounds to play when scrambling, moving tiles, or when puzzle is solved.
  28.     */
  29.    private AudioClip scrambledMusic;
  30.    private AudioClip solvedMusic;
  31.    private AudioClip moveMusic;
  32.  
  33.    /*
  34.     * Image to use for puzzle (does not include border).
  35.     */
  36.    private Image tileImage;
  37.  
  38.    /*
  39.     * Entire puzzle image (including borders) as currently being 
  40.     * displayed on the screen.
  41.     */
  42.    private Image puzImage = null;
  43.  
  44.   /*
  45.     * Sizes of various parts of the puzzle.
  46.     */   
  47.    private int numCols;
  48.    private int numRows;
  49.    private Dimension tileSize;
  50.    private Dimension borderSize = new Dimension(5,5);
  51.    private static final Dimension minBorder = new Dimension(0,0);
  52.  
  53.    /*
  54.     * Width of "cut" between tiles.
  55.     */
  56.    private int cutWidth = 0;
  57.  
  58.    /*
  59.     * Number of steps to use when animating tile move
  60.     */
  61.    private int animSteps;
  62.  
  63.    /*
  64.     * Array to keep track of the current position of each tile.
  65.     * When solved, the numbers in this array are in order across the
  66.     * rows.  For programming convenience, this array has a border of
  67.     * tiles that are not part of the puzzle.  For example, in a 
  68.     * 4 x 4 puzzle the array would look like:
  69.     *<pre>
  70.     *           0  1  2  3  4  5
  71.     *           6  7  8  9 10 11
  72.     *          12 13 14 15 16 17
  73.     *          18 19 20 21 22 23
  74.     *          24 25 26 27 28 29
  75.     *          30 31 32 33 34 35
  76.     *</pre>
  77.     */
  78.    private int[][] tilePos;
  79.  
  80.    /*
  81.     * Tile number for the blank tile (space in puzzle) and the current
  82.     * row and column location of the blank tile.
  83.     */
  84.    private int blankTileNum;
  85.    private int blankRow;
  86.    private int blankCol;
  87.  
  88.    /*
  89.     * The following variables indicate to the animator which direction
  90.     * to shift, and how many tiles are being shifted.
  91.     * Only one of colShiftDir and rowShiftDir can be non-zero once the
  92.     * animator is called.
  93.     */
  94.    private int colShiftDir;
  95.    private int rowShiftDir;
  96.    private int numColShift;
  97.    private int numRowShift;
  98.  
  99.    /*
  100.     * The physical coordinates of the upper left hand corner of the 
  101.     * tiles to move, along with their destination coordinates.
  102.     */
  103.    private Point moveFrom = new Point(0,0);
  104.    private Point moveTo = new Point(0,0);
  105.  
  106.    /*
  107.     * Variables used to indicate the current state of the puzzle, or
  108.     * actions taking place.
  109.     */
  110.    private boolean solved = true; 
  111.    private boolean animating = false;
  112.    private boolean doScramble = false;
  113.  
  114.    /*
  115.     * Thread for animation.
  116.     */
  117.    private Thread animator = null;
  118.  
  119.    /*
  120.     * MediaTracker object used to load user supplied tile image.
  121.     */
  122.    private MediaTracker tracker;
  123.  
  124.    
  125.    /**
  126.     * Creates a slide puzzle component.
  127.     *
  128.     * @param Cols number of columns in puzzle
  129.     * @param Rows number of rows in puzzle
  130.     * @param Image image to be used for the tile area of the puzzle
  131.     * @param blankRow row number where the empty space should appear
  132.     *                 in puzzle (first row is 1)
  133.     * @param blankCol col number where the empty space should appear
  134.     *                 in puzzle (first col is 1)
  135.     * @param cutWidth amount of space between puzzle pieces (can be 0)
  136.     * @param scramble music to play while scrambling puzzle
  137.     * @param solved music to play when puzzle is solved
  138.     * @param move music to play when moving tiles
  139.     */
  140.    public SlidePuzzle(int Rows, int Cols, Image tileImage, int blankRow,
  141.               int blankCol, int cutWidth, 
  142.               AudioClip scramble, AudioClip solved, AudioClip move) {
  143.  
  144.      scrambledMusic = scramble;
  145.      solvedMusic = solved;
  146.      moveMusic = move;
  147.  
  148.      this.tileImage = tileImage;
  149.   
  150.      /*
  151.       * Start loading image
  152.       */
  153.      tracker = new MediaTracker(this);
  154.      tracker.addImage(this.tileImage, 0);
  155.  
  156.      numCols = Cols;
  157.      numRows = Rows;
  158.      if (cutWidth > 0)
  159.        this.cutWidth = cutWidth;
  160.  
  161.      /*
  162.       * Initialize tile position array
  163.       */
  164.      tilePos = new int[numRows+2][numCols+2];
  165.  
  166.      for (int col = 0; col <= numCols+1; col++) 
  167.        for (int row = 0; row <= numRows+1; row++)
  168.      tilePos[row][col] = row*(numCols+2) + col;
  169.  
  170.      /*
  171.       * Check to make sure we have legal blankRow and blankCol
  172.       * arguments.  If so, copy them to local, otherwise use
  173.       * defaults.
  174.       */
  175.      if ((blankRow < 1) || (blankRow > Rows))
  176.        this.blankRow = Rows;
  177.      else
  178.        this.blankRow = blankRow;
  179.      if ((blankCol < 1) || (blankCol > Cols))
  180.        this.blankCol = Cols;
  181.      else
  182.        this.blankCol = blankCol;
  183.  
  184.      blankTileNum = this.blankRow * (numCols+2) + this.blankCol;
  185.      /*
  186.       * We need to know the size of the image, and have it loaded
  187.       * before we are done with the constructor
  188.       */
  189.      try {
  190.        tracker.waitForID(0);
  191.      } catch (InterruptedException e) { }
  192.  
  193.      tileSize = new Dimension((tileImage.getWidth(null) +  cutWidth + 
  194.                    numCols - 1) / numCols,
  195.                   (tileImage.getHeight(null) + cutWidth +
  196.                    numRows - 1) / numRows);
  197.      resize(preferredSize());
  198.  
  199.      /*
  200.       * Start up thread for animation.
  201.       */
  202.      //start();
  203.    }
  204.  
  205.    /**
  206.     * Layout takes care of creating the puzImage from the tileImage
  207.     * if the puzzle had not been laid out yet.  Creates
  208.     * the borders and puts in lines between the tiles.  If the
  209.     * puzzle had already been laid out, then this must be a resize 
  210.     * request, so we copy from the existing puzImage.
  211.     */
  212.    public void layout() {
  213.  
  214.      super.layout();
  215.      Dimension d = size();
  216.      Image oldPuz = puzImage;
  217.      Dimension oldBorderSize = borderSize;
  218.  
  219.      puzImage = createImage(d.width, d.height);
  220.      Graphics g = puzImage.getGraphics();
  221.  
  222.  
  223.      g.setColor(getBackground());
  224.      g.fillRect(0,0,d.width, d.height);
  225.      g.setColor(getForeground());
  226.      /*
  227.       * Compute the border size and draw a nice 3d border around the
  228.       * tiles.  The border fills any leftover area between the tile
  229.       * image size and the actual size of the component.
  230.       */     
  231.      borderSize = new Dimension ((d.width - numCols*tileSize.width -
  232.                   cutWidth) / 2,
  233.                  (d.height - numRows*tileSize.height -  
  234.                   cutWidth) / 2);
  235.  
  236.      Color lightBorder = getForeground().brighter().brighter();
  237.      Color darkBorder =  getForeground().darker().darker();
  238.  
  239.      int end = Math.max(borderSize.width, borderSize.height);
  240.      for (int i = 0; i < end; i++) {
  241.        if (i < end * 3 / 4) {
  242.      g.setColor(lightBorder);
  243.        } else {
  244.      g.setColor(darkBorder);
  245.        }
  246.        g.drawLine(i * borderSize.width / end, i * borderSize.height / end, 
  247.           i * borderSize.width / end, 
  248.           d.height - i * borderSize.height / end);
  249.        g.drawLine(i * borderSize.width / end, i * borderSize.height / end,
  250.           d.width - i * borderSize.width / end, 
  251.           i * borderSize.height / end);
  252.        if (i >= end * 3 / 4) {
  253.      g.setColor(lightBorder);
  254.        } else {
  255.      g.setColor(darkBorder);
  256.        }
  257.        g.drawLine(d.width - i * borderSize.width / end, 
  258.           i * borderSize.height / end, 
  259.           d.width - i * borderSize.width / end, 
  260.           d.height - i * borderSize.height / end);
  261.        g.drawLine(i * borderSize.width / end, 
  262.           d.height - i * borderSize.height / end,
  263.           d.width - i * borderSize.width / end, 
  264.           d.height - i * borderSize.height / end);
  265.      }
  266.  
  267.      /*
  268.       * If this is the first time in layout (oldPuz == null), then we
  269.       * draw in the tileImage, and then put in the cut lines between the
  270.       * tiles.  Otherwise copy old image of puzzle to new image.
  271.       */
  272.      if (oldPuz == null) {
  273.        g.drawImage(tileImage, (d.width - tileImage.getWidth(null)) / 2,
  274.            (d.height - tileImage.getHeight(null)) / 2, this);
  275.        
  276.        g.setColor(getBackground());
  277.        for (int i = 0; i <= numCols; i++) 
  278.      for (int j = 0; j < cutWidth; j++) 
  279.        g.drawLine(i * tileSize.width + borderSize.width + j, 
  280.               borderSize.height, 
  281.               i * tileSize.width + borderSize.width + j, 
  282.               d.height - borderSize.height - 1);
  283.        for (int i = 0; i <= numRows; i++)
  284.      for (int j = 0; j < cutWidth; j++)
  285.        g.drawLine(borderSize.width, 
  286.               i * tileSize.height +  borderSize.height + j, 
  287.               d.width - borderSize.width - 1,
  288.               i * tileSize.height + borderSize.height + j);
  289.        
  290.        g.fillRect((blankCol - 1) * tileSize.width + borderSize.width + cutWidth,
  291.           (blankRow - 1) * tileSize.height + borderSize.height + 
  292.           cutWidth, tileSize.width, tileSize.height);
  293.      } else {
  294.        g.clipRect(borderSize.width, borderSize.height, 
  295.           tileSize.width * numCols + cutWidth,
  296.           tileSize.height * numRows + cutWidth);
  297.        g.drawImage(oldPuz, borderSize.width - oldBorderSize.width, 
  298.            borderSize.height - oldBorderSize.height, this);
  299.      }
  300.      g.dispose();
  301.      start();
  302.    }
  303.        
  304.  
  305.    /**
  306.     * Preferred size of the component.
  307.     */
  308.    public Dimension preferredSize() {
  309.      return minimumSize();
  310.    }
  311.    
  312.    /**
  313.     * Minimum size of the component.  Minimum size is just size of the tiles
  314.     * plus the size of the smallest border.
  315.     */
  316.    public Dimension minimumSize() {
  317.      return new Dimension(tileSize.width * numCols + (numCols + 1) * cutWidth +
  318.               2 * minBorder.width,
  319.               tileSize.height * numRows + (numRows + 1) * cutWidth
  320.               + 2 * minBorder.height);
  321.    }
  322.  
  323.    /**
  324.     * Resize the component.  We never allow the component to be shrunk
  325.     * smaller than minimumSize, since we have no way of effectively resizing
  326.     * the user supplied tile image.
  327.     *
  328.     * @see #minimumSize
  329.     */
  330.    public synchronized void reshape (int x, int y, int width, int height) {
  331.      Dimension d = minimumSize();
  332.      width = Math.max(width, d.width);
  333.      height = Math.max(height, d.height);
  334.      super.reshape(x, y, width, height);
  335.    }
  336.    
  337.    /**
  338.     * Start the animator thread
  339.     */
  340.    public void start() {
  341.      animator = new Thread(this);
  342.      doScramble = true;
  343.      animator.start();
  344.    }
  345.  
  346.    /**
  347.     * Stop the animator thread
  348.     */
  349.    public void stop() {
  350.       if (animator != null) animator.stop();
  351.       animator = null;
  352.    }
  353.  
  354.    /**
  355.     * Check to see if the puzzle is currently in a solved state.
  356.     *
  357.     * @returns true if puzzle is solved, false otherwise.
  358.     */
  359.    private boolean checkSolution() {
  360.      for (int col = 1; col <= numCols; col++) 
  361.        for (int row = 1; row <= numRows; row++) 
  362.      if (tilePos[row][col] != row * (numCols + 2) + col)
  363.        return false;
  364.  
  365.      return true;
  366.    }
  367.  
  368.  
  369.    /**
  370.     * Handle mouse down event.
  371.     */
  372.    public boolean mouseDown(Event e, int x, int y) {
  373.      /*
  374.       * Convert x and y to coordinates within the tile images.
  375.       * and find the row and column number of tile that has been clicked
  376.       * on.
  377.       */
  378.      x = x - borderSize.width - cutWidth;
  379.      y = y - borderSize.height - cutWidth;
  380.      int tileRow = (y / tileSize.height) + 1;
  381.      int tileCol = (x / tileSize.width) + 1;
  382.  
  383.      /*
  384.       * If user clicked on one of the tiles, (not border)
  385.       * and if we are not currently doing animation, then we
  386.       * process mouse event, otherwise ignore it.
  387.       */
  388.      if ((tileRow >= 1) && (tileRow <= numRows) &&
  389.      (tileCol >= 1) && (tileCol <= numCols) && !animating) {
  390.  
  391.        /*
  392.         * If the puzzle is already solved, then scramble the puzzle.
  393.         * Set flags, and wake up the animator.
  394.         */
  395.        if (solved && !doScramble) {
  396.      doScramble = true;
  397.      animating = true;
  398.      animator.resume();
  399.        } else {
  400.  
  401.          /* 
  402.           * Potential tile move situation.  Compute how far away the
  403.           * empty slot in puzzle is relative to where the mouse down
  404.           * event occurred.
  405.           */
  406.      numColShift = blankCol - tileCol;
  407.      numRowShift = blankRow -  tileRow;
  408.        
  409.          /*
  410.           * Its a legal move only if the blank is in the same row or
  411.           * or same column of the tile that was clicked on.
  412.           */
  413.      if (((numColShift == 0) || (numRowShift == 0)) && 
  414.          (numColShift != numRowShift)) {
  415.  
  416.            /*
  417.             * Set up shifting variables appropriately depending on
  418.             * whether we are shifting a row of tiles, or a column of
  419.             * tiles.  Also set up the animSteps variable since we know
  420.             * at this point whether we are shifting rows or columns.
  421.             */
  422.        if (numColShift == 0) {
  423.          numColShift = 1;
  424.          colShiftDir = 0;
  425.          rowShiftDir = numRowShift / Math.abs(numRowShift);
  426.          numRowShift = Math.abs(numRowShift);
  427.          animSteps = Math.min(Math.max(3, tileSize.height / 10), 10);
  428.        } else {
  429.          numRowShift = 1;
  430.          rowShiftDir = 0;
  431.          colShiftDir = numColShift / Math.abs(numColShift);
  432.          numColShift = Math.abs(numColShift);
  433.          animSteps = Math.min(Math.max(3, tileSize.width / 10), 10);
  434.        }
  435.          
  436.            /*
  437.             * Depending on which of the four directions we are
  438.             * shifting, set up the moveFrom, moveTo variables,
  439.             * fix up the tile positions in the puzzle, and fix up the
  440.             * position of the blank tile.
  441.             */
  442.        if (colShiftDir == 1) {
  443.          moveFrom.move(borderSize.width + cutWidth + 
  444.                (tileCol - 1) * tileSize.width,
  445.                borderSize.height + cutWidth + 
  446.                (tileRow - 1) * tileSize.height);
  447.          moveTo.move(moveFrom.x + tileSize.width, moveFrom.y);
  448.          for (int i = tileCol + numColShift; i > tileCol; i--) 
  449.          tilePos[tileRow][i] = tilePos[tileRow][i - 1];
  450.          tilePos[tileRow][tileCol] = blankTileNum;
  451.          blankRow = tileRow;
  452.          blankCol = tileCol;
  453.        } else if (colShiftDir == -1) {
  454.          tileCol = tileCol - numColShift + 1;
  455.          moveFrom.move(borderSize.width + cutWidth + 
  456.                (tileCol - 1) * tileSize.width,
  457.                borderSize.height + cutWidth + 
  458.                (tileRow - 1) * tileSize.height);
  459.          moveTo.move(moveFrom.x - tileSize.width, moveFrom.y);
  460.          for (int i = tileCol; i < tileCol+numColShift; i++) 
  461.            tilePos[tileRow][i-1] = tilePos[tileRow][i];
  462.          tilePos[tileRow][tileCol + numColShift - 1] = blankTileNum;
  463.          blankRow = tileRow;
  464.          blankCol = tileCol + numColShift - 1;
  465.        } else if (rowShiftDir == 1) {
  466.          moveFrom.move(borderSize.width + cutWidth + 
  467.                (tileCol - 1) * tileSize.width,
  468.                borderSize.height + cutWidth + 
  469.                (tileRow - 1) * tileSize.height);
  470.          moveTo.move(moveFrom.x, moveFrom.y + tileSize.height);
  471.          for (int i = tileRow + numRowShift; i > tileRow; i--) 
  472.            tilePos[i][tileCol] = tilePos[i - 1][tileCol];
  473.          tilePos[tileRow][tileCol] = blankTileNum;
  474.          blankRow = tileRow;
  475.          blankCol = tileCol;
  476.        } else if (rowShiftDir == -1) {
  477.          tileRow = tileRow - numRowShift + 1;
  478.          moveFrom.move(borderSize.width + cutWidth + 
  479.                (tileCol - 1) * tileSize.width,
  480.                borderSize.height + cutWidth + 
  481.                (tileRow - 1) * tileSize.height);
  482.          moveTo.move(moveFrom.x, moveFrom.y - tileSize.height);
  483.          for (int i = tileRow; i < tileRow+numRowShift; i++) 
  484.            tilePos[i-1][tileCol] = tilePos[i][tileCol];
  485.          tilePos[tileRow + numRowShift - 1][tileCol] = blankTileNum;
  486.          blankRow = tileRow + numRowShift - 1;
  487.          blankCol = tileCol;
  488.        }
  489.            
  490.            /* 
  491.             * Check to see if the puzzle is solved, and then wake up
  492.             * animator to do tile move.
  493.             */
  494.        solved = checkSolution();
  495.        animating = true;
  496.        animator.resume();
  497.      }
  498.        }
  499.        return true;
  500.      }
  501.      return super.mouseDown(e, x, y);
  502.    }
  503.  
  504.  
  505.   /*
  506.    * Animator thread.  Sits in a suspended state, until awoken by the mouse
  507.    * down event handler.  Then it checks the state variables to see if it
  508.    * should scramble the puzzle or do a move.
  509.    */
  510.   public void run() {
  511.  
  512.     while (true) {
  513.       if (doScramble) 
  514.     scramblePuzzle();
  515.       else 
  516.     doMove();
  517.       animating = false;
  518.       animator.suspend();
  519.     }                
  520.   }
  521.  
  522.  
  523.   /**
  524.    * Perform a sequence of random moves to scramble the puzzle.
  525.    */
  526.   private void scramblePuzzle() {
  527.     int rowOffset, colOffset;
  528.     int row, col;
  529.     Graphics myG;
  530.  
  531.     if (scrambledMusic != null) 
  532.       scrambledMusic.play();
  533.  
  534.     Graphics pg = puzImage.getGraphics();
  535.     pg.setColor(getBackground());
  536.  
  537.     /* 
  538.      * Randomize the puzzle.  Wait between each move
  539.      * a small amount of time.  We redraw after each move so the user
  540.      * can "follow" the puzzle being scrambled :-)
  541.      */
  542.     
  543.     int numMoves = Math.max(200, numRows * numCols * numRows * numCols);
  544.     for (int i = 0; i < numMoves; i++) {
  545.       do
  546.     rowOffset = (int)(Math.random() * 3) - 1;
  547.       while (rowOffset > 1);
  548.       do
  549.     colOffset = (int)(Math.random() * 3) - 1;
  550.       while (colOffset > 1);
  551.  
  552.       if (!((rowOffset != 0) && (colOffset != 0)) &&
  553.       (blankCol + colOffset > 0) && (blankCol + colOffset <= numCols) &&
  554.        (blankRow + rowOffset > 0) && (blankRow + rowOffset <= numRows)) {
  555.     row = blankRow + rowOffset;
  556.     col = blankCol + colOffset;
  557.          
  558.     pg.copyArea(borderSize.width + cutWidth +  (col - 1) * tileSize.width, 
  559.             borderSize.height + cutWidth + (row - 1) * tileSize.height,
  560.             tileSize.width, tileSize.height, 
  561.             (blankCol - col) * tileSize.width, 
  562.             (blankRow - row) * tileSize.height);
  563.          
  564.     pg.fillRect(borderSize.width + cutWidth + (col - 1) * tileSize.width, 
  565.             borderSize.height + cutWidth + (row - 1) * tileSize.height,
  566.             tileSize.width, tileSize.height);
  567.  
  568.     tilePos[blankRow][blankCol] = tilePos[row][col];
  569.     tilePos[row][col] = blankTileNum;
  570.     blankCol = col;
  571.     blankRow = row;
  572.      
  573.     //myG = getGraphics();
  574.     //myG.clipRect(borderSize.width + cutWidth + tileSize.width *
  575.     //     ((blankCol < col? blankCol: col) - 1),
  576.     //     borderSize.height + cutWidth + tileSize.height *
  577.     //     ((blankRow < row? blankRow: row) - 1),
  578.     //     (Math.abs(colOffset) + 1) * tileSize.width, 
  579.     //     (Math.abs(rowOffset) + 1) * tileSize.height);
  580.     //paint(myG);
  581.         //myG.dispose();
  582.       }
  583.     }
  584.     paint(getGraphics());
  585.     pg.dispose();
  586.     solved = false;
  587.     doScramble = false;
  588.   }
  589.  
  590.  
  591.   /**
  592.    * Do an animated move of the tiles in the puzzle.
  593.    * All drawing is done in the offscreen image puzImage, then copied
  594.    * to the main screen with appropriate setting of the clipping rectangle.
  595.    */
  596.   private void doMove() {
  597.     Point myStart = new Point(moveFrom.x, moveFrom.y);
  598.     Rectangle clip1, clip2;
  599.     Graphics pg;
  600.     Graphics myG;
  601.     Point prevFrom = new Point(0,0);
  602.  
  603.     if (moveMusic != null)
  604.       moveMusic.play();
  605.  
  606.     /*
  607.      * We do animSteps number of steps to move the tiles.
  608.      */
  609.     for (int moveStep = 1; moveStep <= animSteps; moveStep++) {
  610.  
  611.       /* 
  612.        * The first clipping rectangle is just the are that we are moving
  613.        * from.
  614.        */
  615.       clip1 = new Rectangle(moveFrom.x, moveFrom.y,
  616.                   tileSize.width * numColShift, tileSize.height * numRowShift);
  617.  
  618.       /*
  619.        * Copy old move from location to prevFrom, and reset moveFrom to
  620.        * original start position.  This makes it easier to calculate
  621.        * the next moveFrom position.
  622.        */
  623.       prevFrom.move(moveFrom.x, moveFrom.y);
  624.       moveFrom.move(myStart.x, myStart.y);
  625.  
  626.       /* 
  627.        * Calculate where we should move to on this animation step. 
  628.        */
  629.       if (moveStep == animSteps) 
  630.         moveFrom.move(moveTo.x, moveTo.y);
  631.       else
  632.     moveFrom.translate(moveStep * colShiftDir * tileSize.width / animSteps,
  633.              moveStep * rowShiftDir * tileSize.height / animSteps);
  634.  
  635.       /*
  636.        * The overall clipping region is the union of the old moveFrom and
  637.        * the new moveFrom 
  638.        */
  639.       clip2 = clip1.union(new Rectangle(moveFrom.x, moveFrom.y,
  640.                         tileSize.width * numColShift,
  641.                     tileSize.height * numRowShift));
  642.  
  643.       /*
  644.        * Do the actual image move on the puzImage.  
  645.        */
  646.       pg = puzImage.getGraphics();
  647.       pg.clipRect(clip2.x, clip2.y, clip2.width, clip2.height);
  648.       pg.copyArea(prevFrom.x, prevFrom.y, tileSize.width * numColShift, 
  649.                   tileSize.height * numRowShift, moveFrom.x - prevFrom.x, 
  650.                   moveFrom.y - prevFrom.y);
  651.  
  652.       /*  
  653.        * Fill in with background color where the new blank area appears.
  654.        */
  655.       pg.setColor(getBackground());
  656.       if (colShiftDir == 1) 
  657.         pg.fillRect(prevFrom.x, prevFrom.y, moveFrom.x - prevFrom.x, 
  658.                     tileSize.height);
  659.       else if (colShiftDir == -1)
  660.     pg.fillRect(moveFrom.x + (tileSize.width * numColShift),
  661.                 prevFrom.y, prevFrom.x - moveFrom.x, tileSize.height);
  662.       else if (rowShiftDir == 1)
  663.     pg.fillRect(prevFrom.x, prevFrom.y, tileSize.width, 
  664.                     moveFrom.y - prevFrom.y);
  665.       else if (rowShiftDir == -1)
  666.         pg.fillRect(prevFrom.x, moveFrom.y + tileSize.height * numRowShift, 
  667.                     tileSize.width, prevFrom.y - moveFrom.y );
  668.       pg.dispose();
  669.  
  670.       /* 
  671.        * Copy the changed image data to the screen.
  672.        */
  673.       myG = getGraphics();
  674.       myG.clipRect(clip2.x, clip2.y, clip2.width, clip2.height);
  675.       myG.drawImage(puzImage, 0, 0, this);
  676.       myG.dispose();
  677.     
  678.       /* 
  679.        * If we are done and solved, play music.  Otherwise, if not done
  680.        * sleep for 1/10 second and then do next animation step.
  681.        */
  682.       if (moveStep == animSteps) {
  683.     if ((solved) && (solvedMusic != null))
  684.       solvedMusic.play();
  685.       } else {
  686.          try {
  687.            Thread.sleep(100);
  688.          } catch (InterruptedException e) {
  689.            break;
  690.          }
  691.       }
  692.     }
  693.   }
  694.  
  695.   /**
  696.    * Since our puzzle covers the entire area of the component, no
  697.    * need to clear the background before calling paint.
  698.    */
  699.   public void update(Graphics g) {
  700.     paint(g);
  701.   }
  702.  
  703.   /*
  704.    * Make sure the slide puzzle has been validated before we attempt to
  705.    * paint it!
  706.    */
  707.   public void paint(Graphics g) {
  708.     if (!isValid()) validate();
  709.     g.drawImage(puzImage, 0,0, this);
  710.   }
  711.        
  712. }
  713.  
  714.  
  715.  
  716.  
  717.